import { Request, Response, NextFunction } from 'express';
import jwt, { JwtPayload } from 'jsonwebtoken';
import config from '@config/index';
import logger from '@utils/logger';
import User, { IUser } from '@database/models/User';

interface TokenPayload extends JwtPayload {
  userId: string;
  email: string;
  role: string;
}

// JWT Secret constant - fallback if env is not loaded
const JWT_SECRET_FALLBACK = 'devops-smartbot-super-secret-key-20251004-change-in-production';

// Helper function to get JWT secret - with proper type narrowing
function getJWTSecret(): string {
  const envSecret: string | undefined = config.env.JWT_SECRET;
  if (typeof envSecret === 'string' && envSecret.length > 0) {
    return envSecret;
  }
  return JWT_SECRET_FALLBACK;
}

// Extend Express Request to include user
declare global {
  namespace Express {
    interface Request {
      user?: IUser;
    }
  }
}

/**
 * JWT Authentication Middleware
 * Verifies the access token from Authorization header and fetches full user object
 */
export const authenticate = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
  try {
    // Get token from Authorization header
    const authHeader = req.headers.authorization;

    if (!authHeader) {
      res.status(401).json({
        success: false,
        message: 'No authorization token provided',
      });
      return;
    }

    // Extract token (format: "Bearer <token>")
    const parts = authHeader.split(' ');
    if (parts.length !== 2 || parts[0] !== 'Bearer') {
      res.status(401).json({
        success: false,
        message: 'Invalid authorization header format. Use: Bearer <token>',
      });
      return;
    }

    const token = parts[1];

    // Verify token using JWT secret (with fallback)
    const jwtSecret: string = (config.env.JWT_SECRET || JWT_SECRET_FALLBACK) as string;
    const decoded = jwt.verify(token, jwtSecret);

    // Validate and extract payload
    if (typeof decoded === 'object' && decoded !== null && 'userId' in decoded) {
      const tokenPayload = decoded as TokenPayload;

      // Fetch the full user object from database
      const user = await User.findById(tokenPayload.userId)
        .populate('organizations.organization')
        .populate('teams.team')
        .populate('globalRoles');

      if (!user) {
        res.status(401).json({
          success: false,
          message: 'User not found',
        });
        return;
      }

      req.user = user;
    } else {
      res.status(401).json({
        success: false,
        message: 'Invalid token payload',
      });
      return;
    }

    next();
  } catch (error) {
    if (error instanceof jwt.TokenExpiredError) {
      res.status(401).json({
        success: false,
        message: 'Token expired',
        code: 'TOKEN_EXPIRED',
      });
      return;
    }

    if (error instanceof jwt.JsonWebTokenError) {
      res.status(401).json({
        success: false,
        message: 'Invalid token',
        code: 'INVALID_TOKEN',
      });
      return;
    }

    logger.error('Authentication error:', error as Error);
    res.status(500).json({
      success: false,
      message: 'Authentication failed',
    });
  }
};

/**
 * Role-based Authorization Middleware
 * Checks if user has required role(s)
 */
export const authorize = (...allowedRoles: string[]) => {
  return (req: Request, res: Response, next: NextFunction): void => {
    try {
      const user = req.user;

      if (!user) {
        res.status(401).json({
          success: false,
          message: 'Unauthorized - No user information',
        });
        return;
      }

      // Check if user role is in allowed roles
      if (!allowedRoles.includes(user.role)) {
        res.status(403).json({
          success: false,
          message: 'Forbidden - Insufficient permissions',
          requiredRoles: allowedRoles,
          userRole: user.role,
        });
        return;
      }

      next();
    } catch (error) {
      logger.error('Authorization error:', error as Error);
      res.status(500).json({
        success: false,
        message: 'Authorization failed',
      });
    }
  };
};

/**
 * Optional Authentication Middleware
 * Attaches user info if token is present, but doesn't require it
 */
export const optionalAuthenticate = async (
  req: Request,
  res: Response,
  next: NextFunction
): Promise<void> => {
  try {
    const authHeader = req.headers.authorization;

    if (!authHeader) {
      // No token, continue without user info
      next();
      return;
    }

    const parts = authHeader.split(' ');
    if (parts.length === 2 && parts[0] === 'Bearer') {
      const token = parts[1];

      try {
        const jwtSecret: string = (config.env.JWT_SECRET || JWT_SECRET_FALLBACK) as string;
        const decoded = jwt.verify(token, jwtSecret);

        // Validate and extract payload
        if (typeof decoded === 'object' && decoded !== null && 'userId' in decoded) {
          const tokenPayload = decoded as TokenPayload;

          // Fetch the full user object from database
          const user = await User.findById(tokenPayload.userId)
            .populate('organizations.organization')
            .populate('teams.team')
            .populate('globalRoles');

          if (user) {
            req.user = user;
          }
        }
      } catch (error) {
        // Invalid token, but continue anyway
        logger.warn('Optional authentication failed:', error as Error);
      }
    }

    next();
  } catch (error) {
    logger.error('Optional authentication error:', error as Error);
    next();
  }
};

export default { authenticate, authorize, optionalAuthenticate };
